package ns3

import (
	"encoding/json"
	"fmt"
	"strings"
	"sync"
)

type Space struct {
	T    float64 `json:"t"`
	Node string  `json:"node"`
	X    float64 `json:"x"`
	Y    float64 `json:"y"`
}

type Spaces []Space

var mu sync.Mutex

func (spaces *Spaces) Replace(spa Space) bool {
	replaceable, olderIndex, brandNew := spaces.Has(spa)
	//log.Println("olderIndex", olderIndex)
	real := *spaces
	if replaceable {
		mu.Lock()
		//log.Println("spaces.length before:", len(*spaces))
		*spaces = append(real[:olderIndex], real[olderIndex+1:]...)
		//log.Println("spaces.length middle:", len(*spaces))
		*spaces = append(*spaces, spa)

		mu.Unlock()

		return true
	}

	if brandNew {
		*spaces = append(*spaces, spa)
		return true
	}

	return false
}

func (spaces *Spaces) Has(new Space) (replaceable bool, index int, brandNew bool) {
	replaceable = false
	brandNew = true
	for in, s := range *spaces {
		if s.T < new.T && s.Node == new.Node {
			replaceable = true
			index = in
		}

		if s.Node == new.Node {
			brandNew = false
		}
	}

	return
}

func (spaces *Spaces) FindOlder(new Space) int {
	for index, s := range *spaces {
		if s.T < new.T && s.Node == new.Node {
			return index
		}
	}

	return -1
}

func (spaces Spaces) String() string {
	return fmt.Sprintf("%#v", spaces)
}

type NodeRange struct {
	Node  string `json:"node"`
	Range string `json:"range"`
}

type NodeTopology struct {
	NodeFrom string `json:"nodeFrom"`
	NodeTo   string `json:"nodeTo"`
}

type Blockchain struct {
	Throughput int `json:"throughput"`
	Success    int `json:"success"`
}

type NodePhyMacRxTx struct {
	Node       string `json:"node"`
	PhyRxBytes int64  `json:"phyRxBytes"`
	PhyTxBytes int64  `json:"phyTxBytes"`
	MacRxBytes int64  `json:"macRxBytes"`
	MacTxBytes int64  `json:"macTxBytes"`
}

type InstantAccumulativeTraffic struct {
	T     float64
	Bytes int64
}

type IAT = InstantAccumulativeTraffic

type NodeRxTx struct {
	Node    string `json:"node"`
	RxBytes int64  `json:"rxBytes"`
	TxBytes int64  `json:"txBytes"`
}

//type NodeRxTx = PhyMacRxTx
//type NodePhyRxTx = RxTx
//type NodeMacRxTx = RxTx

type AccumulativeTraffic struct {
	PhyRx IAT `json:"phyRx"`
	PhyTx IAT `json:"phyTx"`
	MacRx IAT `json:"macRx"`
	MacTx IAT `json:"macTx"`
}

type Stats struct {
	Interval               int     `json:"interval"`
	SpacePeriodView        []Space `json:"spacePeriodView"`
	SpacePeriodViewPointer *Spaces `json:"-"`
	//PhyMacPeriodView []NodePhyMacRxTx `json:"phyMacPeriodView"`
	NodesAccumulativeTrafficView []NodePhyMacRxTx `json:"nodesAccumulativeTrafficView"`
	//PhyView          []NodePhyRxTx `json:"phyView"`
	//MacView          []NodeMacRxTx `json:"macView"`
	InstantAccumulativeTrafficView AccumulativeTraffic `json:"instantAccumulativeTrafficView"`
	LogView                        []string            `json:"logView"`
	BlockView                      Blockchain          `json:"blockView"`

	RangeView    []NodeRange    `json:"rangeView"`
	TopologyView []NodeTopology `json:"topologyView"`
}

// clean up SpacePeriodView,LogView
func (stats *Stats) FlushPartViews(interval int) {
	stats.Interval = interval
	//stats.SpacePeriodView = make([]Space, 0)
	if stats.SpacePeriodViewPointer == nil { // remain a strimed,unique space array
		spaces := make(Spaces, 0)
		stats.SpacePeriodView = spaces
		stats.SpacePeriodViewPointer = &spaces
	}
	stats.LogView = make([]string, 0)
}

func (stats *Stats) FlushViews(interval int) {
	stats.Interval = interval
	spaces := make(Spaces, 0)
	stats.SpacePeriodView = spaces
	stats.SpacePeriodViewPointer = nil

	stats.NodesAccumulativeTrafficView = make([]NodePhyMacRxTx, 0)
	stats.InstantAccumulativeTrafficView = AccumulativeTraffic{}
	stats.LogView = make([]string, 0)
	stats.BlockView = Blockchain{}
}

func (stats *Stats) appendLog(text string) {
	stats.LogView = append(stats.LogView, text)
}

func (stats *Stats) appendSpace(space Space) {
	//stats.SpacePeriodView = append(stats.SpacePeriodView, space)
	//log.Println("SpacePeriodView--", stats.SpacePeriodViewPointer)
	//log.Println("replace result:", stats.SpacePeriodViewPointer.Replace(space))
	stats.SpacePeriodViewPointer.Replace(space)
}

func (stats *Stats) AddPhyRx(node string, iat IAT) {
	if stats.InstantAccumulativeTrafficView.PhyRx.Bytes == 0 {
		stats.InstantAccumulativeTrafficView.PhyRx = iat
	} else {
		old := stats.InstantAccumulativeTrafficView.PhyRx
		stats.InstantAccumulativeTrafficView.PhyRx = IAT{
			T:     iat.T,
			Bytes: old.Bytes + iat.Bytes,
		}
	}

	//i, found := sort.Find(len(stats.NodesAccumulativeTrafficView), func(i int) int {
	//	return strings.Compare(node, stats.NodesAccumulativeTrafficView[i].Node)
	//})
	i, found := findNode(stats.NodesAccumulativeTrafficView, node)
	if found {
		stats.NodesAccumulativeTrafficView[i].PhyRxBytes = stats.NodesAccumulativeTrafficView[i].PhyRxBytes + iat.Bytes
	} else {
		stats.NodesAccumulativeTrafficView = append(stats.NodesAccumulativeTrafficView, NodePhyMacRxTx{
			Node:       node,
			PhyRxBytes: iat.Bytes,
		})
	}
}

func (stats *Stats) AddPhyTx(node string, iat IAT) {
	if stats.InstantAccumulativeTrafficView.PhyTx.Bytes == 0 {
		stats.InstantAccumulativeTrafficView.PhyTx = iat
	} else {
		old := stats.InstantAccumulativeTrafficView.PhyTx
		stats.InstantAccumulativeTrafficView.PhyTx = IAT{
			T:     iat.T,
			Bytes: old.Bytes + iat.Bytes,
		}
	}

	//i, found := sort.Find(len(stats.NodesAccumulativeTrafficView), func(i int) int {
	//	return strings.Compare(node, stats.NodesAccumulativeTrafficView[i].Node)
	//})
	i, found := findNode(stats.NodesAccumulativeTrafficView, node)
	if found {
		stats.NodesAccumulativeTrafficView[i].PhyTxBytes = stats.NodesAccumulativeTrafficView[i].PhyTxBytes + iat.Bytes
	} else {
		stats.NodesAccumulativeTrafficView = append(stats.NodesAccumulativeTrafficView, NodePhyMacRxTx{
			Node:       node,
			PhyTxBytes: iat.Bytes,
		})
	}
}

func (stats *Stats) AddMacRx(node string, iat IAT) {
	if stats.InstantAccumulativeTrafficView.MacRx.Bytes == 0 {
		stats.InstantAccumulativeTrafficView.MacRx = iat
	} else {
		old := stats.InstantAccumulativeTrafficView.MacRx
		stats.InstantAccumulativeTrafficView.MacRx = IAT{
			T:     iat.T,
			Bytes: old.Bytes + iat.Bytes,
		}
	}

	//i, found := sort.Find(len(stats.NodesAccumulativeTrafficView), func(i int) int {
	//	return strings.Compare(node, stats.NodesAccumulativeTrafficView[i].Node)
	//})
	i, found := findNode(stats.NodesAccumulativeTrafficView, node)
	if found {
		stats.NodesAccumulativeTrafficView[i].MacRxBytes = stats.NodesAccumulativeTrafficView[i].MacRxBytes + iat.Bytes
	} else {
		stats.NodesAccumulativeTrafficView = append(stats.NodesAccumulativeTrafficView, NodePhyMacRxTx{
			Node:       node,
			MacRxBytes: iat.Bytes,
		})
	}
}

func (stats *Stats) AddMacTx(node string, iat IAT) {
	if stats.InstantAccumulativeTrafficView.MacTx.Bytes == 0 {
		stats.InstantAccumulativeTrafficView.MacTx = iat
	} else {
		old := stats.InstantAccumulativeTrafficView.MacTx
		stats.InstantAccumulativeTrafficView.MacTx = IAT{
			T:     iat.T,
			Bytes: old.Bytes + iat.Bytes,
		}
	}

	//i, found := sort.Find(len(stats.NodesAccumulativeTrafficView), func(i int) int {
	//	return strings.Compare(node, stats.NodesAccumulativeTrafficView[i].Node)
	//})
	i, found := findNode(stats.NodesAccumulativeTrafficView, node)
	if found {
		stats.NodesAccumulativeTrafficView[i].MacTxBytes = stats.NodesAccumulativeTrafficView[i].MacTxBytes + iat.Bytes
	} else {
		stats.NodesAccumulativeTrafficView = append(stats.NodesAccumulativeTrafficView, NodePhyMacRxTx{
			Node:       node,
			MacTxBytes: iat.Bytes,
		})
	}
}

func (stats *Stats) ToBytes() []byte {
	if stats == nil {
		return make([]byte, 0)
	}

	jsonBytes, _ := json.Marshal(stats)
	return jsonBytes
}

func (stats *Stats) IsRubbish() bool {
	return len(stats.SpacePeriodView) == 0
}

func findNode(nodes []NodePhyMacRxTx, name string) (index int, found bool) {
	index = -1
	found = false

	for idx, node := range nodes {
		if strings.Compare(node.Node, name) == 0 {
			index = idx
			found = true
		}
	}

	return
}
